package com.sec.android.app.myfiles.feature.optimizestorage;

import android.content.Context;
import android.database.Cursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.LongSparseArray;

import com.sec.android.app.myfiles.feature.OptimizeStorageMgr.AnalysisResult;
import com.sec.android.app.myfiles.feature.OptimizeStorageMgr.StorageDbType;
import com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.GoogleDriveQueryInfo;
import com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.MediaProviderQueryInfo;
import com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.OptimizeStorageQueryInfo;
import com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.QueryInfo;
import com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.SamsungDriveQueryInfo;
import com.sec.android.app.myfiles.info.AppFeatures;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.module.optimizestorage.OptimizeStorageDbTableInfo;
import com.sec.android.app.myfiles.util.UiUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.sec.android.app.myfiles.feature.OptimizeStorageMgr.StorageDbType.MEDIA_PROVIDER;

/**
 * Created by daesu83.kim on 2016-10-19.
 */
public class DuplicatedFileFinder implements FileFinder {

    private static final String COLUMN_NAME_SUB_GROUP_ID = "sub_group_id";
    private static final int SQLITE_MAX_VARIABLE_COUNT = 999;

    private Context mContext;
    private Map<StorageDbType, QueryInfo> mQueryInfoMap = new HashMap<>();


    public DuplicatedFileFinder(Context context) {
        mContext = context;

        mQueryInfoMap.put(StorageDbType.MEDIA_PROVIDER, new MediaProviderQueryInfo());
        mQueryInfoMap.put(StorageDbType.OPTIMIZE_STORAGE, new OptimizeStorageQueryInfo());
        mQueryInfoMap.put(StorageDbType.CLOUD_GOOGLE_DRIVE, new GoogleDriveQueryInfo(mContext));
        if (AppFeatures.isSupportSamsungDrive()) {
            mQueryInfoMap.put(StorageDbType.CLOUD_SAMSUNG_DRIVE, new SamsungDriveQueryInfo(mContext));
        }
    }

    @Override
    public void find(List<AnalysisResult> results) {
        if (results != null) {
            List<AnalysisResult> analysisResults = getDuplicatedFiles();
            if (analysisResults != null) {
                results.addAll(analysisResults);
            }
        }
    }

    private List<AnalysisResult> getDuplicatedFiles() {
        List<AnalysisResult> ret = null;
        if (AppFeatures.isCloudGatewaySupportSamsungDrive() && UiUtils.isCloudGatewayEnabled(mContext)) {
            ret = getDuplicatedFilesInAll();
        } else {
            AnalysisResult result = getDuplicatedFiles(MEDIA_PROVIDER, null, null, null, true);
            if (result != null) {
                ret = new ArrayList<>();
                ret.add(result);
            }
        }
        return ret;
    }

    private List<AnalysisResult> getDuplicatedFilesInAll() {
        List<AnalysisResult> ret = new ArrayList<>();
        AnalysisResult result;
        List<QueryInfo> queryInfos = new ArrayList<>();
        queryInfos.add(mQueryInfoMap.get(StorageDbType.MEDIA_PROVIDER));
        queryInfos.add(mQueryInfoMap.get(StorageDbType.CLOUD_GOOGLE_DRIVE));
        if (AppFeatures.isSupportSamsungDrive()) {
            queryInfos.add(mQueryInfoMap.get(StorageDbType.CLOUD_SAMSUNG_DRIVE));
        }

        LongSparseArray<Integer> sizeCounter = new LongSparseArray<>();
        Integer count;
        for (QueryInfo queryInfo : queryInfos) {
            List<Long> sizes = getAllSizes(queryInfo);
            if (sizes != null) {
                for (Long size : sizes) {
                    count = sizeCounter.get(size);
                    if (count == null) {
                        sizeCounter.put(size, 1);
                    } else {
                        sizeCounter.put(size, count + 1);
                    }
                }
            }
        }

        List<Long> intersectedSizes = new ArrayList<>();
        int sizeCounterSize = sizeCounter.size();
        Long key;
        for (int i = 0; i < sizeCounterSize; i++) {
            key = sizeCounter.keyAt(i);
            Integer size = sizeCounter.get(key);
            if (size != null && size > 1) {
                intersectedSizes.add(key);
            }
        }

        for (QueryInfo queryInfo : queryInfos) {
            result = getDuplicatedFiles(queryInfo, null, null, intersectedSizes, true);
            if (result != null) {
                ret.add(result);
            }
        }

        return ret;
    }

    public List<Long> getAllSizes(QueryInfo queryInfo) {
        List<Long> ret = null;
        Uri uri = queryInfo.getUri();
        String sizeColumnName = queryInfo.getSizeColumnName();

        if (uri != null && !TextUtils.isEmpty(sizeColumnName)) {
            String[] projection = new String[]{sizeColumnName};
            String selection = queryInfo.getAllSizeSelection();
            String orderBy = sizeColumnName + " desc";

            try (Cursor c = mContext.getContentResolver().query(uri, projection, selection, null, orderBy)) {
                if (c != null) {
                    ret = new ArrayList<>();
                    if (c.moveToFirst()) {
                        do {
                            ret.add(c.getLong(0));
                        } while (c.moveToNext());
                    }
                }
            }
        }
        return ret;
    }

    public DuplicatedFileGroupInfo getDuplicatedFileGroupInfo(QueryInfo queryInfo,
                                                              String defaultSelection,
                                                              String[] defaultSelectionArgs,
                                                              List<Long> intersectedSizes,
                                                              boolean isRefresh) {
        DuplicatedFileGroupInfo ret = null;
        Uri uri = queryInfo.getUri();
        String sizeColumnName = queryInfo.getSizeColumnName();
        if (uri != null) {
            ArrayList<Integer> groupChildCount;
            ArrayList<Long> groupSize;
            String[] projection = new String[]{"COUNT(*)", sizeColumnName};
            String selection = queryInfo.getDuplicatedFileGroupInfoSelection(isRefresh);
            String orderBy = queryInfo.getDuplicatedFileGroupInfoOrderBy();

            if (!TextUtils.isEmpty(defaultSelection)) {
                selection = defaultSelection + " AND " + selection;
            }

            try (Cursor c = mContext.getContentResolver().query(uri,
                    projection, selection, defaultSelectionArgs, orderBy)) {
                if (c != null) {
                    int childCount;
                    long childSize;
                    long totalSize = 0L;
                    int intersectedCount = intersectedSizes != null ?
                            intersectedSizes.size() : 0;
                    ret = new DuplicatedFileGroupInfo();
                    groupChildCount = new ArrayList<>();
                    groupSize = new ArrayList<>();
                    if (c.moveToFirst()) {
                        do {
                            childCount = c.getInt(0);
                            if (childCount > 0) {
                                groupChildCount.add(childCount);
                                childSize = c.getLong(1);
                                groupSize.add(childSize);
                                totalSize += (childCount * childSize);
                            }
                        } while (c.moveToNext());
                    }
                    for (int i = 0; i < intersectedCount; i++) {
                        groupChildCount.add(1);
                        groupSize.add(intersectedSizes.get(i));
                        totalSize += intersectedSizes.get(i);
                    }
                    ret.mGroupChildCount = toIntArray(groupChildCount);
                    ret.mGroupSize = toLongArray(groupSize);
                    ret.mTotalSize = totalSize;
                }
            }
        }
        return ret;
    }

    private int[] toIntArray(List<Integer> list) {
        int[] ret = new int[list.size()];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = list.get(i).intValue();
        }
        return ret;
    }

    private long[] toLongArray(List<Long> list) {
        long[] ret = new long[list.size()];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = list.get(i).longValue();
        }
        return ret;
    }

    public AnalysisResult getDuplicatedFiles(StorageDbType storageDbType, String defaultSelection,
                                             String[] defaultSelectionArgs, List<Long> intersectedSizes,
                                             boolean isRefresh) {
        return getDuplicatedFiles(mQueryInfoMap.get(storageDbType),
                defaultSelection, defaultSelectionArgs, intersectedSizes, isRefresh);
    }

    public AnalysisResult getDuplicatedFiles(QueryInfo queryInfo, String defaultSelection,
                                             String[] defaultSelectionArgs, List<Long> intersectedSizes,
                                             boolean isRefresh) {
        AnalysisResult ret = null;
        Uri uri = queryInfo.getUri();
        String titleColumnName = queryInfo.getNameColumnName();
        String sizeColumnName = queryInfo.getSizeColumnName();

        if (uri != null && !TextUtils.isEmpty(sizeColumnName) && !TextUtils.isEmpty(titleColumnName)) {
            DuplicatedFileGroupInfo groupInfo = getDuplicatedFileGroupInfo(queryInfo,
                    defaultSelection, defaultSelectionArgs, intersectedSizes, isRefresh);
            if (groupInfo != null && groupInfo.mGroupSize != null) {
                Cursor result = getResultCursor(queryInfo, groupInfo, defaultSelection, defaultSelectionArgs);
                if (result != null) {
                    Bundle extras = new Bundle();
                    result.setExtras(extras);
                    extras.putIntArray(OptimizeStorageDbTableInfo.EXTRAS_KEY_GROUP_CHILD_COUNT,
                            groupInfo.mGroupChildCount);
                    extras.putLong(OptimizeStorageDbTableInfo.EXTRAS_KEY_TOTAL_SIZE, groupInfo.mTotalSize);
                    extras.putLongArray(OptimizeStorageDbTableInfo.EXTRAS_KEY_DUPLICATED_FILES_FILE_SIZE,
                            groupInfo.mGroupSize);
                    ret = new AnalysisResult();
                    ret.mCursor = result;
                    setResultInfo(ret, queryInfo.getStorageDbType());
                }
            } else {
                Log.e(this, "getDuplicatedFiles - groupInfo is null");
            }
        }
        return ret;
    }

    public Cursor getResultCursor(QueryInfo queryInfo, DuplicatedFileGroupInfo groupInfo,
                                  String defaultSelection, String[] defaultSelectionArgs) {
        Cursor result;
        Uri uri = queryInfo.getUri();
        String titleColumnName = queryInfo.getNameColumnName();
        String sizeColumnName = queryInfo.getSizeColumnName();
        int groupCount = groupInfo.mGroupSize.length;
        StringBuilder selection;
        String[] selectionArgs;
        String orderBy;
        if (queryInfo.getStorageDbType() == StorageDbType.OPTIMIZE_STORAGE) {
            orderBy = sizeColumnName + " desc, " + COLUMN_NAME_SUB_GROUP_ID + " asc, path asc";
        } else {
            orderBy = sizeColumnName + " desc, " + titleColumnName + " asc";
        }

        int repeatCount = (groupCount / SQLITE_MAX_VARIABLE_COUNT) + 1;
        int defaultSelectionArgsLength = (defaultSelectionArgs != null ? defaultSelectionArgs.length : 0);
        Cursor[] cursors = new Cursor[repeatCount];
        for (int repeatIndex = 0; repeatIndex < repeatCount; repeatIndex++) {
            int subGroupCount = repeatIndex < repeatCount - 1 ?
                    SQLITE_MAX_VARIABLE_COUNT : groupCount % SQLITE_MAX_VARIABLE_COUNT;
            selection = new StringBuilder(sizeColumnName + " IN (");
            selectionArgs = new String[subGroupCount + defaultSelectionArgsLength];
            for (int i = 0; i < subGroupCount; i++) {
                selectionArgs[i + defaultSelectionArgsLength] = String.valueOf(groupInfo.mGroupSize[repeatIndex * SQLITE_MAX_VARIABLE_COUNT + i]);
                selection.append('?');
                if (i < subGroupCount - 1) {
                    selection.append(',');
                }
            }
            selection.append(')');
            if (!TextUtils.isEmpty(defaultSelection)) {
                selection.insert(0, defaultSelection + " AND ");
            }
            if (defaultSelectionArgsLength > 0) {
                System.arraycopy(defaultSelectionArgs, 0, selectionArgs, 0, defaultSelectionArgsLength);
            }

            cursors[repeatIndex] = mContext.getContentResolver().query(uri,
                    null, selection.toString(), selectionArgs, orderBy);
        }

        if (cursors.length == 1) {
            result = cursors[0];
        } else {
            result = new MergeCursor(cursors);
        }

        return result;
    }

    private void setResultInfo(AnalysisResult result, StorageDbType storageDbType) {
        if (result != null) {
            result.mType = FileRecord.OptimizeStorageType.DuplicateFiles;
            result.mStorageDbType = storageDbType;
        }
    }

    public static class DuplicatedFileGroupInfo {
        public int[] mGroupChildCount;
        public long[] mGroupSize;
        public long mTotalSize;
    }
}
